package com.gmrz.webauthn.protocol.v1.validation;

import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.Constants.Operation;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.gmrz.webauthn.protocol.v1.processor.WebAuthnErrorCode;
import com.gmrz.webauthn.protocol.v1.schema.CollectedClientData;
import com.gmrz.webauthn.protocol.v1.schema.HashAlgorithmIdentifier;
import com.gmrz.webauthn.protocol.v1.schema.TokenBinding;
import com.gmrz.webauthn.protocol.v1.schema.TokenBindingStatus;
import com.gmrz.webauthn.protocol.v1.schema.WebOperationType;
import com.gmrz.uaf.server.ServerConfig;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class CollectedClientDataValidator
        implements WebAuthnValidator<CollectedClientData>
{
    private static final Logger LOG = LogManager.getLogger(CollectedClientDataValidator.class);
    private ServerConfig webAuthnTenantConfig;
    private Constants.Operation op;
    private String appID;
    private String serverDataChallenge;

    public CollectedClientDataValidator(ServerConfig webAuthnTenantConfig, Constants.Operation op,String appID,String serverDataChallenge)
    {
        setWebAuthnTenantConfig(webAuthnTenantConfig);
        setOp(op);
        this.appID = appID;
        this.serverDataChallenge = serverDataChallenge;
    }

    public Constants.Operation getOp()
    {
        return this.op;
    }

    public void setOp(Constants.Operation op)
    {
        this.op = op;
    }

    public ServerConfig getWebAuthnTenantConfig()
    {
        return this.webAuthnTenantConfig;
    }

    public void setWebAuthnTenantConfig(ServerConfig webAuthnTenantConfig)
    {
        this.webAuthnTenantConfig = webAuthnTenantConfig;
    }

    public void validate(CollectedClientData clientData)
            throws WebAuthnValidationException
    {
        if (clientData == null)
        {
            String errormsg = "Failed to validate ClientData. ClientData is null.";
            logAndThrowException(errormsg, WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
        }
        byte[] challenge = clientData.getChallenge();
        String c = Convert.toBase64(challenge);
        if ((challenge == null) || (challenge.length == 0)) {
            logAndThrowException("challenge in ClientData is null/empty.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
        }
        if(!c.equals(serverDataChallenge)){
            logAndThrowException("challenge in ClientData is error.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
        }
        Set<String> facetIDs = null;
        String origin = clientData.getOrigin();
        try
        {
            facetIDs = this.webAuthnTenantConfig.getFacetIDs(appID);
        }
        catch (Exception e)
        {
            logAndThrowException("Failed to read configured facetIDs.", WebAuthnErrorCode.STATUS_FACETID_VALIDATION_FAILED);
        }
        if ((origin == null) || (!facetIDs.contains(origin)))
        {
            String errormsg = "origin[" + origin + "] not found in the set[" + facetIDs + "]";
            logAndThrowException(errormsg, WebAuthnErrorCode.STATUS_FACETID_VALIDATION_FAILED);
        }
        String androidPackageName = clientData.getAndroidPackageName();
        if(Strings.isNotBlank(androidPackageName)){
            if(!facetIDs.contains(androidPackageName)){
                String errormsg = "androidPackageName[" + androidPackageName + "] not found in the set[" + facetIDs + "]";
                logAndThrowException(errormsg, WebAuthnErrorCode.STATUS_FACETID_VALIDATION_FAILED);
            }
        }
        HashAlgorithmIdentifier hashAlg = clientData.getHashAlg();
        if (hashAlg == null) {
            logAndThrowException("hashAlg in ClientData is null/empty.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
        }
        if (this.op == Constants.Operation.REG)
        {
            if (clientData.getType() != WebOperationType.CREATE) {
                logAndThrowException("client data web operation type mismatch for reg op.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
            }
        }
        else if ((this.op == Constants.Operation.AUTH) &&
                (clientData.getType() != WebOperationType.GET)) {
            logAndThrowException("client data web operation type mismatch for auth op.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
        }
        TokenBinding tokenBinding = clientData.getTokenBinding();
        if (tokenBinding != null)
        {
            if (tokenBinding.getStatus() == null) {
                logAndThrowException("tokenbinding object must contain a valid status.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
            }
            if ((tokenBinding.getStatus() == TokenBindingStatus.PRESENT) && (
                    (tokenBinding.getId() == null) || (tokenBinding.getId().isEmpty()))) {
                logAndThrowException("tokenbinding status is PRESENT, id must be present in this case.", WebAuthnErrorCode.STATUS_CLIENTDATA_VALIDATION_FAILED);
            }
        }
    }

    private void logAndThrowException(String msg, WebAuthnErrorCode error)
            throws WebAuthnValidationException
    {
        LOG.error(msg);
        throw new WebAuthnValidationException(error, msg);
    }
}

